
#include <BFont>
#include <BKeyEvent>
#include <BCharEvent>
#include <BMouseEvent>
#include <BPainter>
#include <BStyle>
#include <BSystem>

#include <BRealSpinBox>
#include "member_BRealSpinBox.h"
#include "member_BSpinBox.h"

using namespace BWE;

#define member					(*(member_BRealSpinBox*)_ptr)
#define member_allocate()		_ptr = new member_BRealSpinBox(this)
#define member_release()		delete (member_BRealSpinBox*)_ptr

BRealSpinBox::BRealSpinBox(BWidget* parent) : BWidget(parent)
{
	member_allocate();
	member.freshBitNum();
	this->setCursor(Cursor_Ibeam);
}
BRealSpinBox::BRealSpinBox(BReal minimum, BReal maximum, BWidget* parent) : BWidget(parent)
{
	member_allocate();
	member.range.set(minimum, maximum);
	member.freshBitNum();
	this->setCursor(Cursor_Ibeam);
}
BRealSpinBox::BRealSpinBox(BReal minimum, BReal maximum, int decimals, BWidget* parent) : BWidget(parent)
{
	member_allocate();
	member.range.set(minimum, maximum);
	member.decimals = decimals;
	member.freshBitNum();
	this->setCursor(Cursor_Ibeam);
}
BRealSpinBox::~BRealSpinBox()
{
	member_release();
}

void BRealSpinBox::setAlign(Align align)
{
	if (member.align != align)
	{
		member.align = align;
		member.dirty = true;
		this->fresh();
	}
}
Align BRealSpinBox::align() const
{
	return member.align;
}

bool BRealSpinBox::setValue(BReal value)
{
	value = member.range.limit(value);
	if (member.value != value)
	{
		member.value = value;
		member.block.text.reset(value, member.decimals);
		member.block.reset();
		member.dirty = true;
		emit(Signal_ValueChanged, member.value);
		this->fresh();
		return true;
	}
	return false;
}
BReal BRealSpinBox::value() const
{
	return member.value;
}

void BRealSpinBox::setDecimals(int decimals)
{
	if (member.decimals != decimals)
	{
		member.decimals = decimals;
		member.block.text.reset(member.value, member.decimals);
		member.dirty = true;
		this->fresh();
	}
}
int BRealSpinBox::decimals() const
{
	return member.decimals;
}

void BRealSpinBox::setMinimum(BReal minimum)
{
	setRange(minimum, member.range.max());
}
BReal BRealSpinBox::minimum() const
{
	return member.range.min();
}

void BRealSpinBox::setMaximum(BReal maximum)
{
	setRange(member.range.min(), maximum);
}
BReal BRealSpinBox::maximum() const
{
	return member.range.max();
}

void BRealSpinBox::setRange(BReal minimum, BReal maximum)
{
	setRange(BRanged(minimum, maximum));
}
void BRealSpinBox::setRange(const BRealRange& range)
{
	if (member.range != range)
	{
		member.range = range;
		BReal value = member.range.limit(member.value);
		this->setValue(value);
		emit(Signal_RangeChanged, member.range);

		BString minStr(member.range.min(), 0);
		BString maxStr(member.range.max(), 0);
		member.bitNum = bMax(minStr.size(), maxStr.size()) + member.decimals;
	}
}
const BRealRange& BRealSpinBox::range() const
{
	return member.range;
}

void BRealSpinBox::setPrefix(const BString& prefix)
{
	if (member.prefix != prefix)
	{
		member.prefix = prefix;
		member.dirtySize = true;
		member.dirty = true;
		this->fresh();
	}
}
const BString& BRealSpinBox::prefix() const
{
	return member.prefix;
}

void BRealSpinBox::setSuffix(const BString& suffix)
{
	if (member.suffix != suffix)
	{
		member.suffix = suffix;
		member.dirtySize = true;
		member.dirty = true;
		this->fresh();
	}
}
const BString& BRealSpinBox::suffix() const
{
	return member.suffix;
}

void BRealSpinBox::setStep(BReal step)
{
	if (member.step != step)
	{
		member.step = step;
		emit(Signal_StepChanged, step);
	}
}
BReal BRealSpinBox::step() const
{
	return member.step;
}

void BRealSpinBox::setButtonShown(bool buttonShown)
{
	if (!member.upButton && buttonShown)
	{
		member.upButton = new BButton(Graph_Arrow_Up_Fill, this);
		member.upButton->setName("up");
		member.upButton->setForm(Form_IconOnly);

		member.downButton = new BButton(Graph_Arrow_Down_Fill, this);
		member.downButton->setName("down");
		member.downButton->setForm(Form_IconOnly);

		member.connect(member.upButton, Signal_Clicked, &member_BRealSpinBox::slotButtonClicked);
		member.connect(member.downButton, Signal_Clicked, &member_BRealSpinBox::slotButtonClicked);

		member.dirty = true;
		this->fresh();
		return;
	}
	if (member.upButton && !buttonShown)
	{
		this->removeChild(member.upButton);
		this->removeChild(member.downButton);
		member.dirty = true;
		this->fresh();
	}
}
bool BRealSpinBox::buttonShown() const
{
	return member.upButton != 0;
}

void BRealSpinBox::setFormulable(bool formulable)
{
	if (member.formulable != formulable)
	{
		member.formulable = formulable;
	}
}
bool BRealSpinBox::formulable() const
{
	return member.formulable;
}

void BRealSpinBox::setReadOnly(bool readOnly)
{
	if (member.readOnly != readOnly)
	{
		member.readOnly = readOnly;
		if (member.upButton)
		{
			member.upButton->setEnabled(false);
			member.downButton->setEnabled(false);
		}
		this->fresh();
	}
}
bool BRealSpinBox::readOnly() const
{
	return member.readOnly;
}

BSize BRealSpinBox::sizeHint() const
{
	BSize hsize = BWidget::sizeHint();
	const BFont* font = this->font();
	if (font)
	{
		int cw = font->codeWidth('0');
		hsize.width() += cw * member.bitNum;
		hsize.height() += font->size() + 3;
		if (member.dirtySize)
		{
			member.freshSizes();
			member.dirtySize = false;
		}
		if (member.prefixSize.valid())
		{
			hsize.width() += member.prefixSize.width();
			hsize.height() = bMax(hsize.height(), member.prefixSize.height());
		}
		if (member.suffixSize.valid())
		{
			hsize.width() += member.suffixSize.width();
			hsize.height() = bMax(hsize.height(), member.suffixSize.height());
		}
	}
	return hsize;
}

void BRealSpinBox::updateEvent(const BEvent& event)
{
	if (this->focused() && member.block.inputCount == 0)
	{
		bool blinkShown = true;
		if (member.blinkTime > 0)
		{
			BReal now = event.time();
			BReal time = now - member.blinkTick;
			blinkShown = time <= member.blinkTime / 2;
			if (time > member.blinkTime)
				member.blinkTick = now;
		}
		if (member.blinkShown != blinkShown)
		{
			member.blinkShown = blinkShown;
			this->fresh();
		}
	}
}
void BRealSpinBox::resizeEvent(const BEvent& event)
{
	member.dirty = true;
	BWidget::resizeEvent(event);
}

void BRealSpinBox::styleEvent(const BEvent& event)
{
	if (const BStyle* style = this->realStyle())
	{
		const BValue& alignValue = style->value(Value_Align);
		if (alignValue.valid())
			member.align = alignValue;

		member.block.setFont(this->font());
		member.dirty = true;
	}
	BWidget::styleEvent(event);
}
void BRealSpinBox::freshEvent(const BEvent& event)
{
	if (member.dirty)
	{
		member.freshRects();
		member.dirty = false;
	}
	if (member.dirtyLine)
	{
		member.freshInputLine();
		member.dirtyLine = false;
	}
	BWidget::freshEvent(event);
}
void BRealSpinBox::paintEvent(const BEvent& event)
{
	BPainter painter(this);
	painter.drawBackground();

	if (this->focused() && member.block.inputCount)
	{
		const BRect& crect = this->centerRect();
		const BPoint& pos = member.block.rect.pos();
		painter.setColor(Color_Background_Checked);
		int begin = bMin(member.block.inputIndex, member.block.inputIndex + member.block.inputCount);
		int end = bMax(member.block.inputIndex, member.block.inputIndex + member.block.inputCount);
		for (int i = begin; i < end; i++)
		{
			const CodeCell& cell = member.block.cells[i];
			BRect cellRect = cell.rect + pos;
			painter.fillRect(cellRect);
		}
	}

	if (this->enabled())
	{
		if (member.prefix.size() && painter.setColor(Color_Prefix))
			painter.drawText(member.prefixRect, member.prefix);
		if (member.suffix.size() && painter.setColor(Color_Suffix))
			painter.drawText(member.suffixRect, member.suffix);
		if (painter.setColor(Color_Text))
			member.block.paint(painter);
	}
	else
	{
		if (member.prefix.size() && painter.setColor(Color_Prefix_Disable))
			painter.drawText(member.prefixRect, member.prefix);
		if (member.suffix.size() && painter.setColor(Color_Suffix_Disable))
			painter.drawText(member.suffixRect, member.suffix);
		if (painter.setColor(Color_Text_Disable))
			member.block.paint(painter);
	}

	if (this->focused())
	{
		painter.drawBorder(State_Normal, true);
		painter.drawPerch(State_Normal, true);
		painter.drawMargin(State_Normal, true);
	}
	else
	{
		if (member.hovered)
		{
			painter.drawBorder(State_Hovered);
			painter.drawPerch(State_Hovered);
			painter.drawMargin(State_Hovered);
		}
		else
		{
			painter.drawBorder();
			painter.drawPerch();
			painter.drawMargin();
		}
	}

	if (member.blinkShown && member.block.inputCount == 0)
	{
		if (painter.setColor(Color_Indicator))
		{
			painter.drawLine(member.lineBegin, member.lineEnd);
		}
	}
}

void BRealSpinBox::focusEvent(const BEvent& event)
{
	if (!member.readOnly)
	{
		if (this->focused())
		{
			member.focusedTime = event.time();
			member.block.inputIndex = 0;
			member.block.inputCount = member.block.cells.size();
		}
		else
		{
			if (member.editing)
			{
				member.block.text.reset(member.value, member.decimals);
				member.block.reset();
				member.editing = false;
				emit(Signal_EditFinished, member.value);
			}
			member.blinkShown = false;
		}
		member.dirty = true;
		member.dirtyLine = true;
		this->fresh();
	}
	BWidget::focusEvent(event);
}
void BRealSpinBox::keyDownEvent(const BKeyEvent& keyEvent)
{
	Key key = keyEvent.key();
	if (keyEvent.keyState(Key_LCtrl))
	{
		if (key == Key_C)
		{
			BString text = member.block.selection();
			bool ret = BSystem::SetClipBoardText(text);
		}
		else if (key == Key_V)
		{
			if (member.block.removeSelection())
			{
				member.editing = true;
				member.dirty = true;
			}
			BString text;
			bool ret = BSystem::GetClipBoardText(text);
			BReal value = text.toReal();
			setValue(value);
			member.editing = true;
			member.dirty = true;
			this->emit(Signal_ValueEdited, member.value);
			this->emit(Signal_EditFinished, member.value);
			this->fresh();
		}
	}
	else if (key == Key_Enter)
	{
		this->setFocused(false);
	}
	else if (key == Key_Left)
	{
		if (member.block.inputCount)
		{
			member.block.inputCount = 0;
			member.dirtyLine = true;
			this->fresh();
		}
		else if (member.block.inputIndex > 0)
		{
			member.block.inputIndex--;
			member.dirtyLine = true;
			this->fresh();
		}
		member.blinkTick = keyEvent.time();
	}
	else if (key == Key_Right)
	{
		if (member.block.inputCount)
		{
			member.block.inputIndex += member.block.inputCount;
			member.block.inputCount = 0;
			member.dirtyLine = true;
			this->fresh();
		}
		else if (member.block.inputIndex < member.block.cells.size())
		{
			member.block.inputIndex++;
			member.dirtyLine = true;
			this->fresh();
		}
		member.blinkTick = +500;
	}
	else if (key == Key_Back)
	{
		if (member.block.inputCount)
		{
			if (member.block.removeSelection())
			{
				member.textEdited();
			}
		}
		else
		{
			member.block.inputIndex--;
			if (member.block.remove(member.block.inputIndex))
			{
				member.textEdited();
			}
		}
		member.blinkTick = +500;
	}
	else if (key == Key_Delete)
	{
		if (member.block.inputCount)
		{
			if (member.block.removeSelection())
			{
				member.textEdited();
			}
		}
		else
		{
			if (member.block.remove(member.block.inputIndex))
			{
				member.textEdited();
			}
		}
		member.blinkTick = +500;
	}
	BWidget::keyDownEvent(keyEvent);
}
void BRealSpinBox::keyUpEvent(const BKeyEvent& keyEvent)
{
	this->fresh();
	BWidget::keyUpEvent(keyEvent);
}
void BRealSpinBox::charEvent(const BCharEvent& charEvent)
{
	const BCode& code = charEvent.code();
	if (code == '.')
	{
		if (member.block.inputIndex < 1)
			return;
	}
	bool valid = false;
	if (member.formulable)
	{
		valid = (48 <= code && code < 58) || code == '.' || code == '+' || code == '-' || code == '*' || code == '/' || code == '(' || code == ')' || code == ' ';
	}
	else
	{
		if (code == '-' && member.block.inputIndex != 0)
			return;
		if (code == '.' && member.block.text.contain('.'))
			return;
		valid = (48 <= code && code < 58) || code == '.' || code == '-';
	}
	if (valid)
	{
		member.block.removeSelection();
		member.block.input(code);
		member.textEdited();
	}
	BWidget::charEvent(charEvent);
}

void BRealSpinBox::mouseEnterEvent(const BMouseEvent& mouseEvent)
{
	if (member.hoverIndex != -1)
	{
		member.hoverIndex = -1;
	}
	member.hovered = true;
	this->fresh();
	BWidget::mouseEnterEvent(mouseEvent);
}
void BRealSpinBox::mouseLeaveEvent(const BMouseEvent& mouseEvent)
{
	if (member.hoverIndex != -1)
	{
		member.hoverIndex = -1;
		this->fresh();
	}
	member.hovered = false;
	this->fresh();
	BWidget::mouseLeaveEvent(mouseEvent);
}

void BRealSpinBox::mousePressEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.time() - member.focusedTime < (BReal)0.3)
		return;
	if (mouseEvent.button() == Button_Left)
	{
		const BPoint& pos = mouseEvent.pos();
		int hoverIndex = member.hoverIndex;
		if (member.block.inputCount > 0 || member.block.inputIndex != member.hoverIndex)
		{
			member.block.inputIndex = member.hoverIndex;
			member.block.inputCount = 0;
			member.blinkTick = mouseEvent.time();
			member.dirtyLine = true;
			this->fresh();
		}
		member.pressed = true;
		member.pressedIndex = member.hoverIndex;
	}
	BWidget::mousePressEvent(mouseEvent);
}
void BRealSpinBox::mouseReleaseEvent(const BMouseEvent& mouseEvent)
{
	member.pressed = false;
	this->fresh();
	BWidget::mouseReleaseEvent(mouseEvent);
}
void BRealSpinBox::mouseMoveEvent(const BMouseEvent& mouseEvent)
{
	const BPoint& pos = mouseEvent.pos();
	int index = member.block.indexAt(pos);
	if (member.hoverIndex != index)
	{
		member.hoverIndex = index;
	}
	if (member.pressed)
	{
		int inputCount = member.pressedIndex - member.hoverIndex;
		if (member.block.inputIndex != member.hoverIndex || member.block.inputCount != inputCount)
		{
			member.block.inputIndex = member.hoverIndex;
			member.block.inputCount = inputCount;
			member.dirtyLine = true;
			this->fresh();
		}
	}
	BWidget::mouseMoveEvent(mouseEvent);
}
bool BRealSpinBox::mouseWheelEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.wheel() > 0)
	{
		if (this->setValue(member.value + member.step))
		{
			emit(Signal_ValueEdited, member.value);
			emit(Signal_EditFinished, member.value);
		}
	}
	else
	{
		if (this->setValue(member.value - member.step))
		{
			emit(Signal_ValueEdited, member.value);
			emit(Signal_EditFinished, member.value);
		}
	}
	return true;
}
void BRealSpinBox::mouseDoubleClickEvent(const BMouseEvent& mouseEvent)
{
	member.block.inputIndex = 0;
	member.block.inputCount = member.block.cells.size();
	member.dirtyLine = true;
	this->fresh();
	BWidget::mouseDoubleClickEvent(mouseEvent);
}

